<script lang="ts" setup>
import { storeToRefs } from "pinia";
import { getNeed, getSafe, ProcessType, useStore } from "~/models/data";
import GridLine from "./components/GridLine.vue";
import { compareArrByEachElement, isAllZero, numArrToS, sToNumArr, testStringAllNumber } from "~/utils/funcs";
import * as R from "ramda";
import { ElMessage } from "element-plus";

const state = useStore()

const { processNum, resourceNum, process, avaliable } = storeToRefs(state)

const applyFlag = ref(false)
const releaseFlag = ref(false)
const addFlag = ref(false)

const addForm = reactive({
	max: ''
})

const applyForm = reactive({
	pid: '',
	resource: ''
})

const applyQueue = ref<{
	pid: string,
	res: number[]
}[]>([])

onUnmounted(() => {
	state.$reset()
})

const releaseForm = reactive({
	pid: '',
})

function handleAdd() {
	addFlag.value = false

	const newIt: ProcessType = {
		pid: `p${process.value.length}`,
		max: sToNumArr(addForm.max),
		need: [],
		allocation: Array.from({ length: resourceNum.value }, () => 0),
		status: true
	}
	newIt.need = getNeed(newIt)
	process.value = R.append(newIt)(process.value)
	processNum.value = process.value.length

	refreshSafeState()

	//reset form
	addForm.max = ''
}

const getIndexByPid = (pid: string, arr: ProcessType[]) => {
	for (let i = 0; i < arr.length; i++) {
		if (arr[i].pid === pid) {
			return i 
		}
	}
	return -1 
}

function handleApply(pid: string, resource: string) {
	applyFlag.value = false
	// need -
	// av -
	// allo +

	const res_arr = sToNumArr(resource)
	const idx = getIndexByPid(pid, process.value)

	const nd_arr = process.value[idx].need

	if (!compareArrByEachElement(nd_arr, res_arr)) {
		ElMessage({
			message: '数据不合理！',
			type: 'error'
		})
		return
	}

	const plusWithResource = (arr: number[]) => arr.map((it, i) => it + res_arr[i])
	const minusWithResource = (arr: number[]) => arr.map((it, i) => it - res_arr[i])

	if (compareArrByEachElement(avaliable.value, res_arr)) {
		let work_process = [...process.value]
		work_process[idx] = {
			...work_process[idx],
			// allocation + resource
			allocation: plusWithResource(work_process[idx].allocation),
			// need - resource
			need: minusWithResource(work_process[idx].need)
		}
		let av = minusWithResource(avaliable.value)
		const { isSafe } = getSafe(
			resourceNum.value,
			av,
			work_process
		)

		if (!isSafe) {
			ElMessage({
				message: '操作不安全',
				type: 'warning'
			})
			applyQueue.value = R.append({
				pid: pid,
				res: sToNumArr(resource)
			})(applyQueue.value)
			process.value[idx] = {
				...process.value[idx],
				status: false
			}
			return
		}

		process.value[idx] = {
			...process.value[idx],
			// allocation + resource
			allocation: plusWithResource(process.value[idx].allocation),
			// need - resource
			need: minusWithResource(process.value[idx].need)
		}
		//avaliable - resource
		avaliable.value = minusWithResource(avaliable.value)

	} else {
		ElMessage({
			message: `资源不足，进程${pid}阻塞！`,
			type: 'warning',
		})
		// 阻塞进程
		process.value[idx] = {
			...process.value[idx],
			status: false
		}
		// 申请行为排队
		applyQueue.value = R.append({
			pid,
			res: sToNumArr(resource)
		})(applyQueue.value)
	}

	// const statusQueue = refreshSafeState()
	// if (!safeState.isSafe) {
	// 	process.value = process.value.map((it, i) => ({ ...it, status: statusQueue[i] }))
	// }

	// reset form
	applyForm.resource = ''
	applyForm.pid = ''
}

const releaseByPid = (pid: string) => {
	const idx = getIndexByPid(pid, process.value)
	const allo_arr = process.value[idx].allocation
	const itorPlus = (arr: number[]) => arr.map((it, i) => it + allo_arr[i])
	avaliable.value = itorPlus(avaliable.value)
	process.value.splice(idx, 1)
	processNum.value = process.value.length
}

// 处理阻塞队列
function handleApplyQueue() {
	const newQueue: { pid: string; res: number[]; }[] = []
	const pidMap: Record<string, number> = {}
	process.value.forEach((it, i) => pidMap[it.pid] = i)
	applyQueue.value.forEach(it => {
		const res_arr = it.res
		const work_process = [...process.value]
		// check
		const plusWithResource = (arr: number[]) => arr.map((it, i) => it + res_arr[i])
		const minusWithResource = (arr: number[]) => arr.map((it, i) => it - res_arr[i])
		const idx = pidMap[it.pid]
		work_process[idx] = {
			...work_process[idx],
			allocation: plusWithResource(work_process[idx].allocation),
			need: minusWithResource(work_process[idx].need)
		}
		let av = minusWithResource(avaliable.value)

		// check end

		const { isSafe } = getSafe(
			resourceNum.value,
			av,
			work_process
		)

		if (compareArrByEachElement(avaliable.value, res_arr) && isSafe) {
			handleApply(it.pid, it.res.join(' '))
			process.value[pidMap[it.pid]].status = true
		} else {
			newQueue.push(it)
		}
	});
	applyQueue.value = newQueue
}

function handleRelease(pid: string) {
	releaseFlag.value = false

	if (pid === '') return
	releaseByPid(pid)
	handleApplyQueue()
	refreshSafeState()
	//reset form 
	releaseForm.pid = ''
}

function handleReleaseAll() {
	releaseFlag.value = false
	couldReleaseProcessIds.value.map(it => it.value).forEach(it => releaseByPid(it))
	handleApplyQueue()
	refreshSafeState()
	//reset form 
	releaseForm.pid = ''
}

const router = useRouter()

function reset() {
	router.push('/')
}

const total = computed(() => {
	const av_arr = [...avaliable.value]
	const getAllocation = (arr: ProcessType[]) => arr.map(it => it.allocation)
	const allo_arr_arr = getAllocation(process.value)
	return numArrToS(allo_arr_arr.reduce((pre, cur) => {
		cur.forEach((it, i) => {
			pre[i] += it
		})
		return pre
	}, av_arr))
})

const safeState = reactive({
	isSafe: false,
	safeQueue: [] as number[]
})


function refreshSafeState() {
	const { isSafe, statusQueue, safeQueue } = getSafe(
		resourceNum.value,
		avaliable.value,
		process.value
	)

	safeState.isSafe = isSafe
	safeState.safeQueue = safeQueue

	//console.log('process: ', process.value[process.value.length - 1])
	//console.log('safe: ', safeQueue)
	return statusQueue
}

const statusQueue = refreshSafeState()
process.value = process.value.map((it, i) => ({ ...it, status: statusQueue[i] }))

const clog_queue = computed(() => {
	return process.value.reduce((pre, item) => {
		if (!item.status) {
			pre += item.pid + ' '
		}
		return pre
	}, '')
})

const pid_options = computed(() => {
	return process.value.reduce((p, item) => {
		const needFilter = isAllZero(item.need)
		if (item.status && !needFilter) {
			p.push({
				value: item.pid,
				label: item.pid
			})
		}
		return p
	}, [] as {
		value: string
		label: string
	}[])
})

const couldReleaseProcessIds = computed(() => {
	const getIds = (arr: ProcessType[]) => arr.reduce(((p, c) => {
		if (isAllZero(c.need)) {
			p.push(c.pid)
		}
		return p
	}), [] as string[])
	const genOptions = R.map<string, { value: string; label: string }>(item => ({
		value: item,
		label: item
	}))
	return R.pipe(getIds, genOptions)(process.value)
})


function checkInput(input: string) {
	if (!testStringAllNumber(input.trim().split(' '), resourceNum.value)) {
		ElMessage({
			message: '数据格式错误!',
			type: 'error'
		})
		return false
	}
	return true
}
</script>


<template>
	<div flex="~ col" justify-center items-center>
		<p text-3xl text-blue-500>银行家算法 - 操作界面</p>
		<div>
			<strong>进程数：{{ processNum }}</strong>&nbsp;
			<strong>资源种类数：{{ resourceNum }}</strong>&nbsp;
			<strong>系统资源总数：{{ total }}</strong>&nbsp;
			<strong>系统剩余资源数(available)：{{ avaliable.join(' ') }}</strong>
		</div>
		<el-table :data="process" style="width: 540px" :key="Math.random()">
			<el-table-column prop="pid" label="进程名" width="80" />
			<el-table-column prop="max" label="Max" width="100">
				<template #default="scope">
					<grid-line :arr="scope.row.max" />
				</template>
			</el-table-column>
			<el-table-column prop="allocation" label="Allocation" width="100">
				<template #default="scope">
					<grid-line :arr="scope.row.allocation" />
				</template>
			</el-table-column>
			<el-table-column prop="need" label="Need" width="100">
				<template #default="scope">
					<grid-line :arr="scope.row.need" />
				</template>
			</el-table-column>
			<el-table-column prop="status" label="状态" width="100">
				<template #default="scope">
					<span>{{ scope.row.status ? '运行' : '阻塞' }}</span>
				</template>
			</el-table-column>
		</el-table>
		<div space="x-5 y-10">
			<span>当前系统状态：{{ safeState.isSafe ? '安全' : '不安全' }}</span>
			<span v-if="safeState.isSafe">
				安全序列为：{{ numArrToS(safeState.safeQueue.map(it => process[it].pid)) }}
			</span>
			<span>因资源申请未满足而阻塞的进程：{{ clog_queue.length !== 0 ? clog_queue : "无" }}</span>
		</div>
		<div>
			<button class="btn m-3 text-sm" @click="applyFlag = !applyFlag" :disabled="!safeState.isSafe">
				申请资源
			</button>
			<button class="btn m-3 text-sm" @click="releaseFlag = !releaseFlag" :disabled="!safeState.isSafe">
				释放资源
			</button>
			<button class="btn m-3 text-sm" @click="addFlag = !addFlag" :disabled="!safeState.isSafe">
				新建进程
			</button>
			<button class="btn m-3 text-sm" @click="reset">
				重启
			</button>
		</div>

		<el-dialog v-model="applyFlag" title="申请资源">
			<el-form :model="applyForm">
				<el-form-item label="进程id" label-width="140px">
					<el-select v-model="applyForm.pid" autocomplete="off">
						<el-option v-for="item in pid_options" :key="item.value" :label="item.label" :value="item.value" />
					</el-select>
				</el-form-item>
				<el-form-item label="申请资源数量" label-width="140px">
					<el-input v-model="applyForm.resource" autocomplete="off" />
				</el-form-item>
			</el-form>
			<template #footer>
				<span class="dialog-footer">
					<el-button @click="applyFlag = false">取消</el-button>
					<el-button type="primary"
						@click="() => { if (checkInput(applyForm.resource)) handleApply(applyForm.pid, applyForm.resource); else applyForm.resource = '' }">
						确认
					</el-button>
				</span>
			</template>
		</el-dialog>
		<el-dialog v-model="releaseFlag" title="释放资源">
			<el-form :model="applyForm">
				<el-form-item label="进程id" label-width="140px">
					<el-select v-model="releaseForm.pid" autocomplete="off">
						<el-option v-for="item in couldReleaseProcessIds" :key="item.value" :label="item.label"
							:value="item.value" />
					</el-select>
				</el-form-item>
			</el-form>
			<template #footer>
				<span class="dialog-footer">
					<el-button @click="releaseFlag = false">取消</el-button>
					<el-button @click="() => handleReleaseAll()">释放所有符合条件的进程</el-button>
					<el-button type="primary" @click="() => handleRelease(releaseForm.pid)">释放进程资源</el-button>
				</span>
			</template>
		</el-dialog>
		<el-dialog v-model="addFlag" title="新建进程">
			<el-form :model="addForm">
				<el-form-item label="Max" label-width="140px">
					<el-input v-model="addForm.max" autocomplete="off" />
				</el-form-item>
			</el-form>
			<template #footer>
				<span class="dialog-footer">
					<el-button @click="addFlag = false">取消</el-button>
					<el-button type="primary" @click="() => { if (checkInput(addForm.max)) handleAdd(); else addForm.max = '' }">确认
					</el-button>
				</span>
			</template>
		</el-dialog>
	</div>
</template>


